//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#pragma once

#include "../CSource.h"
#include "GeneratorUtils.h"

namespace Noise
{

	
	//
	// Function Source for Terrace-mapping curve generation
	//

	class CSourceTerrace : public CSource
	{
	// types
	protected:
		typedef CSourceTerrace	Self;


	// variables
	protected:
		pod_array<real>	_controlPoints;
		CSourcePtr		_source;
		bool			_invertTerraces;	// Determines if the terrace-forming curve between all control points is inverted.
		
		static const
			ESource		_sourceType = TERRACE;


	// methods
	public:
		CSourceTerrace () :
			_invertTerraces(false)
		{}

		~CSourceTerrace ()
		{}

		ESourceState GetState () const
		{
			return _source.IsNotNull() ?
					( _controlPoints.Empty() ? UNCOMPLETE_DATA : SOURCE_COMPLETE)
					: UNCOMPLETE_ATTACHMENT_SOURCE;
		}
		
		virtual void Delete ()
		{
			_source = null;
			_controlPoints.ClearMem();
		}

		virtual EResult GetInteger (ESourceQuery type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case SOURCE_STATE :
					v[0] = GetState();
					return OK;

				case SOURCE_TYPE :
					v[0] = _sourceType;
					return OK;

				case SOURCES_COUNT : {
					int src_count = 0;
					_source->GetInteger( type, &src_count, 1 );
					v[0] = src_count + 1;
					return OK;
				}

				case INPUT_SOURCES :
					v[0] = 1;
					return OK;

				case CONTROL_POINTS_COUNT :
					v[0] = _controlPoints.Count();
					return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, CSourcePtr src)
		{
			switch ( type )
			{
				case SOURCE_0 :	_source = src;	return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, const real *v, int count)
		{
			switch ( type )
			{
				case ARG_ADD_CONTROL_POINTS :
					CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );
					return AddPoints( v, count ) ? OK : INVALID_VALUE;

				case ARG_CONTROL_POINTS :
					_controlPoints.ClearMem();
					return AddPoints( v, count ) ? OK : INVALID_VALUE;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, const int *v, int count)
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_INVERT_TERRACES :
					_invertTerraces = !!v[0];
					return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult GetArg (EArgument type, CSourcePtr &src) const
		{
			switch ( type )
			{
				case SOURCE_0 :	src = _source;	return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult GetArg (EArgument type, real *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_CONTROL_POINTS :
					MemCopy( v, _controlPoints.ptr(), Min( count * sizeof(real), _controlPoints.Size() ) );
					return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult GetArg (EArgument type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_INVERT_TERRACES :
					v[0] = _invertTerraces;
					return OK;
			}
			return INVALID_ENUM;
		}

		bool AddPoints (const real *points, int count)
		{
			bool result = true;

			for (int i = 0; i < count; ++i)
			{
				int pos = FindInsertionPos( points[i] );
				
				if ( pos == -1 )
					result = false;

				_controlPoints.Insert( points[i], pos );
			}
			return result;
		}
		
		int FindInsertionPos (real inputValue) const
		{
			uint insertionPos;
			for (insertionPos = 0; insertionPos < _controlPoints.Count(); ++insertionPos)
			{
				if ( inputValue < _controlPoints[insertionPos] )	break;		else
				if ( inputValue == _controlPoints[insertionPos] )	return -1;
			}
			return insertionPos;
		}
		
		virtual CSourcePtr Clone () const override
		{
			Self *	src = new Self();
			src->_invertTerraces = _invertTerraces;
			src->_controlPoints	 = _controlPoints;
			return src;
		}
		
		virtual void PreBuild (const TPreBuildData &data) override
		{
			data.Push( _source.ptr() );
		}

		virtual bool BuildFunction (TFunctionData &data) override
		{
			Utils::CubicMixFunction( data );

			if ( data.AddSourceType( _sourceType ) )
			{
				data.source << TOSTRING(
					real Terrace (real3 vPoint, const real controlPoints[],\n
								  int controlPointsCount, real value, bool invertTerraces)\n
					{\n
						int indexPos;\n
						for (indexPos = 0; indexPos < controlPointsCount; ++indexPos) {\n
							if (value < controlPoints[indexPos]) {\n
								break;\n
							}\n
						}\n
						\n
						int index0 = clamp( indexPos - 1, 0, controlPointsCount - 1 );\n
						int index1 = clamp( indexPos    , 0, controlPointsCount - 1 );\n
						\n
						if ( index0 == index1 ) {\n
							return controlPoints[index1];\n
						}\n
						\n
						real value0 = controlPoints[index0];\n
						real value1 = controlPoints[index1];\n
						real alpha  = (value - value0) / (value1 - value0);\n
						\n
						if ( invertTerraces )\n
						{\n
							alpha = 1.0 - alpha;\n
							real tmp = value0;\n
							value0 = value1;\n
							value1 = tmp;\n
						}\n
						\n
						alpha *= alpha;\n
						return mix( value0, value1, alpha );\n
					}\n\n
				);
			}

			CHECK_VALUE( not _controlPoints.Empty() );

			data.source
				<< "real " << CSource::FunctionName() << _id << " (real3 vPoint)\n{\n"
				<< "const real controlPoints[" << _controlPoints.Count() << "] = {  ";

			FOR( i, _controlPoints )
			{
				data.source
					<< '\n' << _controlPoints[i] << ", ";
			}
			data.source.EraseFromBack(2);
			data.source
				<< " \n};\n"
				<< "return Terrace( vPoint, controlPoints, " << _controlPoints.Count() << ", ";
			
			CSource::BuildFunctionCall( data.source, _source->GetSourceId() )
				<< ", " << _invertTerraces << " );\n}\n";

			return true;
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		Real NativeFunction (const TVec<Real,3> &vPoint)
		{
			CHECK_VALUE_R( not _controlPoints.Empty(), Real() );

			Real value = (_source.ptr()->*NativeFunction)( vPoint );
			
			int indexPos;
			for (indexPos = 0; (uint)indexPos < _controlPoints.Count(); ++indexPos) {
				if (value < _controlPoints[indexPos]) {
					break;
				}
			}
			
			int index0 = Clamp( indexPos - 1, 0, _controlPoints.Count() - 1 );
			int index1 = Clamp( indexPos    , 0, _controlPoints.Count() - 1 );

			if ( index0 == index1 ) {
				return _controlPoints[index1];
			}

			Real value0 = _controlPoints[index0];
			Real value1 = _controlPoints[index1];
			Real alpha  = (value - value0) / (value1 - value0);

			if ( _invertTerraces )
			{
				alpha = Real(1.0) - alpha;
				_swap( value0, value1 );
			}

			alpha *= alpha;
			return Mix( value0, value1, alpha );
		}

		virtual float32 NativeFunction32 (const fvec3 &vPoint) override
		{
			return NativeFunction< float32, &CSource::NativeFunction32 >( vPoint );
		}

		virtual float64 NativeFunction64 (const dvec3 &vPoint) override
		{
			return NativeFunction< float64, &CSource::NativeFunction64 >( vPoint );
		})

		static bool CreateSource (CSourcePtr &src, CContext *context, ESource type)
		{
			ASSERT( type == _sourceType );

			src = new Self();
			return true;
		}
	};

}	// Noise